package org.loon.framework.android.game.action.collision;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;

import org.loon.framework.android.game.core.LObject;
import org.loon.framework.android.game.core.LSystem;
import org.loon.framework.android.game.core.geom.RectBox;
import org.loon.framework.android.game.utils.MathUtils;

/**
 * Copyright 2008 - 2011
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
/**
 * 
 * @project loonframework
 * @author chenpeng
 * @email：ceponline@yahoo.com.cn
 * @version 0.1
 */
public class CollisionObject {

	class TempMatrix {

		public LinkedList<LObject> objects = new LinkedList<LObject>();

	}

	public static final int COLLISION_ONE = 0;

	public static final int COLLISION_MATRIX = 1;

	private TempMatrix[][] places;

	private ArrayList<LObject> objects;

	private int method = 0;

	private int gridSize = 0;

	private int width = 0;

	private int height = 0;

	public CollisionObject(ArrayList<LObject> os) {
		this.objects = os;
		this.method = 0;
	}

	public CollisionObject(ArrayList<LObject> os, int gridSize) {
		this(os, gridSize, LSystem.screenRect.width, LSystem.screenRect.height);
	}

	public CollisionObject(ArrayList<LObject> os, int gs, int w,
			int h) {
		this.method = 1;
		this.objects = os;
		this.gridSize = gs;
		this.width = w;
		this.height = h;
		this.updateMatrix(w, h);
	}

	public void register(LObject o) {
		synchronized (objects) {
			objects.add(o);
		}
	}

	public HashMap<LObject, LObject> runCollisionEngineForCollisions(
			float mapXPosition, float mapYPosition, float xOffset,
			float yOffset, boolean checkNearBy) {
		HashMap<LObject, LObject> collisionOccurrence = new HashMap<LObject, LObject>();
		for (LObject currentSprite : objects) {
			for (LObject conflictingSprite : objects) {
				if (currentSprite != conflictingSprite) {
					if (spritesOverlap(currentSprite, conflictingSprite,
							mapXPosition, mapYPosition, xOffset, yOffset,
							checkNearBy)) {
						collisionOccurrence.put(currentSprite,
								conflictingSprite);
					}
				}
			}
		}
		return collisionOccurrence;
	}

	private boolean spritesOverlap(LObject currentSprite,
			LObject conflictingSprite, float mapXPosition, float mapYPosition,
			float xOffset, float yOffset, boolean checkNearBy) {
		float enlargement = checkNearBy ? 10.0f : 0.0f;
		RectBox currentSpriteShape = new RectBox(getAbsoluteXPosition(
				currentSprite, mapXPosition, xOffset), getAbsoluteYPosition(
				currentSprite, mapYPosition, yOffset), currentSprite.getWidth()
				+ enlargement, currentSprite.getHeight() + enlargement);
		RectBox conflictingSpriteShape = new RectBox(getAbsoluteXPosition(
				conflictingSprite, mapXPosition, 0), getAbsoluteYPosition(
				conflictingSprite, mapYPosition, 0),
				conflictingSprite.getWidth() + enlargement,
				conflictingSprite.getHeight() + enlargement);
		return currentSpriteShape.intersects(conflictingSpriteShape);
	}

	private float getAbsoluteYPosition(LObject currentSprite,
			float mapYPosition, float yOffset) {
		return currentSprite.getY() + MathUtils.abs(mapYPosition) + yOffset;
	}

	private float getAbsoluteXPosition(LObject currentSprite,
			float mapXPosition, float xOffset) {
		return currentSprite.getX() + MathUtils.abs(mapXPosition) + xOffset;
	}

	public ArrayList<LObject> getSprites() {
		return objects;
	}

	private void updateMatrix(int width, int height) {
		this.places = new TempMatrix[width][height];
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				this.places[x][y] = new TempMatrix();
			}
		}
	}

	public LObject findCollision(int x, int y, int layer) {
		for (int i = 0; i < this.objects.size(); i++) {
			LObject obj = (LObject) this.objects.get(i);
			if (obj.getLayer() == layer) {
				if (obj.getCollisionArea() != null
						&& obj.getCollisionArea().contains(x, y)) {
					return obj;
				}
			}
		}
		return null;
	}

	public LObject findCollision(RectBox rect, int layer) {
		if (this.method == COLLISION_ONE) {
			for (int i = 0; i < this.objects.size(); i++) {
				LObject obj = (LObject) this.objects.get(i);
				if ((obj.getLayer() == layer)
						&& (obj.getCollisionArea().intersects(rect))) {
					return obj;
				}
			}
		}
		if (this.method == COLLISION_MATRIX) {
			for (int y = rect.y() / this.gridSize; y < (rect.y + rect.height)
					/ this.gridSize + 1; y++) {
				for (int x = rect.x() / this.gridSize; x < (rect.x + rect.width)
						/ this.gridSize + 1; x++) {
					if ((x < this.width) && (y < this.height)) {
						for (int i = 0; i < this.places[x][y].objects.size(); i++) {
							LObject res = (LObject) this.places[x][y].objects
									.get(i);
							if ((res.getLayer() == layer)
									&& (rect.intersects(res.getCollisionArea()))) {
								return res;
							}
						}
					}
				}
			}

		}

		return null;
	}

	public LObject findCollision(RectBox rect, String ModelName, int layer) {
		if (this.method == COLLISION_ONE) {
			for (int i = 0; i < this.objects.size(); i++) {
				LObject obj = (LObject) this.objects.get(i);
				if (obj.getName() == null) {
					continue;
				}
				if ((obj.getLayer() == layer)
						&& (obj.getName().equals(ModelName))
						&& (obj.getCollisionArea().intersects(rect))) {
					return obj;
				}
			}
		}

		if (this.method == COLLISION_MATRIX) {
			for (int y = rect.y() / this.gridSize; y < (rect.y + rect.height)
					/ this.gridSize + 1; y++) {
				for (int x = rect.x() / this.gridSize; x < (rect.x + rect.width)
						/ this.gridSize + 1; x++) {
					if ((x < this.width) && (y < this.height)) {
						for (int i = 0; i < this.places[x][y].objects.size(); i++) {
							LObject res = (LObject) this.places[x][y].objects
									.get(i);
							if (res.getName() == null) {
								continue;
							}
							if ((res.getLayer() == layer)
									&& (rect.intersects(res.getCollisionArea()))
									&& (res.getName().equals(ModelName))) {
								return res;
							}
						}
					}
				}
			}
		}

		return null;
	}

	public LObject findCollision(LObject object, int layer) {
		if (this.method == COLLISION_ONE) {
			for (int i = 0; i < this.objects.size(); i++) {
				LObject obj = (LObject) this.objects.get(i);
				if ((obj.getLayer() == layer)
						&& (object != obj)
						&& (obj.getCollisionArea().intersects(object
								.getCollisionArea()))) {
					return obj;
				}
			}
		}

		if (this.method == COLLISION_MATRIX) {
			RectBox rect = object.getCollisionArea();
			for (int y = rect.y() / this.gridSize; y < (rect.y + rect.height)
					/ this.gridSize + 1; y++) {
				for (int x = rect.x() / this.gridSize; x < (rect.x + rect.width)
						/ this.gridSize + 1; x++) {
					if ((x < this.width) && (y < this.height)) {
						for (int i = 0; i < this.places[x][y].objects.size(); i++) {
							LObject obj = (LObject) this.places[x][y].objects
									.get(i);
							if ((obj.getLayer() == layer)
									&& (object != obj)
									&& (obj.getCollisionArea()
											.intersects(object
													.getCollisionArea()))) {
								return obj;
							}
						}
					}
				}
			}
		}
		return null;
	}

	public LObject findCollision(LObject object, String name, int layer) {
		if (this.method == COLLISION_ONE) {
			for (int i = 0; i < this.objects.size(); i++) {
				LObject obj = (LObject) this.objects.get(i);
				if (obj.getName() == null) {
					continue;
				}
				if ((obj.getLayer() == layer)
						&& (object != obj)
						&& (obj.getName().equals(name))
						&& (obj.getCollisionArea().intersects(object
								.getCollisionArea()))) {
					return obj;
				}
			}
		}

		if (this.method == COLLISION_MATRIX) {
			RectBox rect = object.getCollisionArea();
			for (int y = rect.y() / this.gridSize; y < (rect.y + rect.height)
					/ this.gridSize + 1; y++) {
				for (int x = rect.x() / this.gridSize; x < (rect.x + rect.width)
						/ this.gridSize + 1; x++) {
					if ((x < this.width) && (y < this.height)) {
						for (int i = 0; i < this.places[x][y].objects.size(); i++) {
							LObject obj = (LObject) this.places[x][y].objects
									.get(i);
							if (obj.getName() == null) {
								continue;
							}
							if ((obj.getLayer() == layer)
									&& (object != obj)
									&& (obj.getName().equals(name))
									&& (obj.getCollisionArea()
											.intersects(object
													.getCollisionArea()))) {
								return obj;
							}
						}
					}
				}
			}
		}

		return null;
	}

	public boolean findCollision(LObject src, LObject obj, int layer) {
		return (src.getLayer() == layer) && (obj.getLayer() == layer)
				&& (src.getCollisionArea().intersects(obj.getCollisionArea()));
	}

	public void clear() {
		if (objects != null) {
			objects.clear();
		}
	}

}
